package net.sf.cldsql.dj.container;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import net.sf.cldsql.dj.container.base.DiAop;
import net.sf.cldsql.dj.container.base.DiBean;
import net.sf.cldsql.dj.mvc.ConnTransAop;
import net.sf.cldsql.dj.mvc.DiService;
import net.sf.cldsql.dj.utl.DiException;
import net.sf.cldsql.dj.utl.Codelet;
import net.sf.cldsql.sql2json.main.SjServlet;

/**
 * Container
 */
public class DiContainer {
    
    final Logger log = LoggerFactory.getLogger(DiContainer.class);
    
    public static final short SINGLETON = 0;
    public static final short NORMAL = 1;
    
    private HashMap<String, DiDef> classMap = new HashMap<String, DiDef>();
    private HashMap<String, Object> singletonMap = new HashMap<String, Object>();
    
    public DiContainer() {
        log.info("Container initialized.");
    }

    public String register(String name, Class cls, Class impl, Class[] paramTypes, Object[] params, short mode, Class aop, String pattern, Codelet initCode) {
        log.debug ("Register bean={} class={} impl={} paramTypes={} params={} mode={} aop={}, initCode={}", 
                name, cls == null ? null : cls.getCanonicalName(), impl == null ? null : impl.getCanonicalName(), paramTypes, mode, aop == null ? null : aop.getClass().getCanonicalName(), initCode);
        DiDef beanDef = new DiDef(name, cls, impl, paramTypes, params, mode, aop, pattern, initCode);
        synchronized(this) {
            if (classMap.get(beanDef.getName()) == null) {
                classMap.put(beanDef.getName(), beanDef);
            } else {
                throw new DiException("Cannot define bean " + name + " again.");
            }
        }
        
        return name;
    }

    public String register(Class cls, Class aop, String pattern, Codelet initCode) {
        return register(cls.getCanonicalName(), cls, null, null, null, NORMAL, aop, pattern, initCode);
    }
    
    public String register(String name, Class cls, Class impl, Class aop, String pattern) {
        return register(name, cls, impl, null, null, NORMAL, aop, pattern, null);
    }

    public String register(Class cls, Class impl, Class[] paramTypes, Object[] params, short mode, Class aop, String pattern, Codelet initCode) {
        return register(cls.getCanonicalName(), cls, impl, paramTypes, params, mode, aop, pattern, initCode);
    }

    public String register(Class cls) {
        return register(cls.getCanonicalName(), cls, null, null, null, NORMAL, null, null, null);
    }

    public String register(Class cls, short mode) {
        return register(cls.getCanonicalName(), cls, null, null, null, mode, null, null, null);
    }

    public String register(String beanName, Class cls, short mode) {
        return register(beanName, cls, null, null, null, mode, null, null, null);
    }

    public String register(Class cls, Codelet initCode) {
        return register(cls.getCanonicalName(), cls, null, null, null, NORMAL, null, null, initCode);
    }
    
    public String register(Class cls, short mode, Codelet initCode) {
        return register(cls.getCanonicalName(), cls, null, null, null, mode, null, null, initCode);
    }
    
    public String register(Class cls, Class[] paramTypes, Object[] params) {
        return register(cls.getCanonicalName(), cls, null, paramTypes, params, NORMAL, null, null, null);
    }
    
    public String register(Class cls, Class[] paramTypes, Object[] params, short mode) {
        return register(cls.getCanonicalName(), cls, null, paramTypes, params, mode, null, null, null);
    }
    
    public String register(Class cls, Class impl, Class[] paramTypes, Object[] params) {
        return register(cls.getCanonicalName(), cls, impl, paramTypes, params, NORMAL, null, null, null);
    }
    
    public String register(Class cls, Class impl, Class[] paramTypes, Object[] params, short mode) {
        return register(cls.getCanonicalName(), cls, impl, paramTypes, params, mode, null, null, null);
    }
    
    public String register(Class cls, Class impl) {
        return register(cls.getCanonicalName(), cls, impl, null, null);
    }

    public String register(Class cls, Class impl, Class aop, String pattern) {
        return register(cls.getCanonicalName(), cls, impl, null, null, NORMAL, aop, pattern, null);
    }
    
    public String register(Class cls, Class impl, short mode, Class aop, String pattern) {
        return register(cls.getCanonicalName(), cls, impl, null, null, mode, aop, pattern, null);
    }
    
    public DiDef find(String name) {
        return classMap.get(name);
    }
    
    public <T> T get(String name) {
        log.debug ("Get name:{}", name);
        try {
            Object instance = null;
            DiDef beanDef = find(name);
            switch (beanDef.getMode()) {
            case SINGLETON:
                instance = singletonMap.get(name);
                if (instance == null) {
                    synchronized(this) {
                        if (instance == null) {
                            log.debug ("Init singleton {} for name {}", beanDef, name);
                            instance = newInstance(beanDef);
                            singletonMap.put(beanDef.getName(), instance);
                        }
                    }
                }
                break;
            case NORMAL:
                instance = newInstance(beanDef);
                break;
            default:
                new DiException("Invalid mode " + beanDef.getMode());
            }
            log.debug ("Return {}", instance);
            return this.<T>convertType(instance);
        } catch (Exception e) {
            throw new DiException(e);
        }
    }
    
    @SuppressWarnings("unchecked")
    public <T> T convertType(Object instance) {
        return (T) instance;
    }
    
    public <T> T get(Class<T> cls) {
        return get(cls.getCanonicalName());
    }
    
    private Object newInstance(DiDef beanDef) {
        Object instance;
        
        log.debug ("New instance {} .", beanDef);
        if (beanDef.getInitCode() != null) {
            log.debug ("Create with initCode {}.", beanDef.getInitCode());
            instance = beanDef.getInitCode().run(beanDef);
        } else {
            Class clazz = beanDef.getImpl() == null ? beanDef.getIntf() : beanDef.getImpl();
            
            if (beanDef.getParams() == null) {
                try {
                    log.debug ("Create without param.");
                    instance = clazz.newInstance();
                } catch (InstantiationException e) {
                    throw new DiException(e);
                } catch (IllegalAccessException e) {
                    throw new DiException(e);
                }
            } else {
                try {
                    log.debug ("Create with param {}.", beanDef.getParamTypes());
                    instance = clazz.getConstructor(beanDef.getParamTypes()).newInstance(beanDef.getParams());
                } catch (IllegalArgumentException e) {
                    throw new DiException(e);
                } catch (SecurityException e) {
                    throw new DiException(e);
                } catch (InstantiationException e) {
                    throw new DiException(e);
                } catch (IllegalAccessException e) {
                    throw new DiException(e);
                } catch (InvocationTargetException e) {
                    throw new DiException(e);
                } catch (NoSuchMethodException e) {
                    throw new DiException(e);
                }
            }
        }
        
        Method setLogMethod;
        if (instance instanceof DiBean) {
            try {
                setLogMethod = instance.getClass().getMethod("setLog", new Class[]{Logger.class});
            } catch (SecurityException e) {
                throw new DiException(e);
            } catch (NoSuchMethodException e) {
                throw new DiException(e);
            }
            
            try {
                setLogMethod.invoke(instance, new Object[] {LoggerFactory.getLogger(instance.getClass())});
            } catch (IllegalArgumentException e) {
                throw new DiException(e);
            } catch (IllegalAccessException e) {
                throw new DiException(e);
            } catch (InvocationTargetException e) {
                throw new DiException(e);
            }
        }
        
        if (beanDef.getAop() != null) {
            log.debug ("Found bean {} has aop and call proxy.", beanDef);
            instance = new DiProxy(this, instance, beanDef.getAop(), beanDef.getPattern()).getProxy();
        }
        
        return instance;
    }

}
